package game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;

import javax.swing.JPanel;

public class GameField extends JPanel implements Runnable{
	private Tank tank;
	
	private Thread gameThread;
	private GameListener listener;
	
	private Graphics dbg;
	private Image dbImage = null;
	
	private static final int PWIDTH = 1000;
	private static final int PHEIGHT = 700;
	
	// Iteration time for game loop
	private final long PERIOD = 10*1000000L; // 10 ms -> nanos
	// Number of frames with a delay of 0 ms before the animation thread yields to other running threads.
	private final int NO_DELAYS_PER_YIELD = 16;
	// Number of frames that can be skipped, i.e the games state is updated but not rendered
	private final int MAX_FRAME_SKIPS = 5;

	private boolean running = false;
	private boolean gameOver = false;
	private boolean isPaused = false;
	
	/**
	 * Constructs a GameField object.
	 */
	public GameField(){
		super();
		this.setPreferredSize(new Dimension(PWIDTH, PHEIGHT));
		listener = new GameListener(this);
		this.addKeyListener(listener);
		
		tank = new Tank(20, 400, 90);
	}
	
	/**
	 * Initialize and start the game Thread.
	 */
	public void newGame(){
		if(gameThread == null || !running){
			gameThread = new Thread(this);
			gameThread.start();
		}
	}
	
	/**
	 * Pause the game.
	 */
	public void pauseGame(){ isPaused = true; }
	
	/**
	 * Resume the game.
	 */
	public void resumeGame(){ isPaused = false; }
	
	/**
	 * Called by the user to stop execution.
	 */
	public void stopGame(){ running = false; }
	
	/**
	 * Called by the listener class when player wants to move the tank
	 * or stop moving the tank.
	 * 
	 * @param direction The direction to move the tank.
	 * @param move True if tank should move in given direction
	 * 			   and false if tank should stop moving in given direction.
	 */
	public void moveTank(String direction, boolean move){
		if(direction.equals("right"))
			tank.moveRight(move);
		else if(direction.equals("left"))
			tank.moveLeft(move);
		else if(direction.equals("forward"))
			tank.moveForward(move);
		else if(direction.equals("reverse"))
			tank.moveReverse(move);
	}
	
	/**
	 * The game loop.
	 */
	@Override
	public void run(){
		long beforeTime, afterTime, timeDiff, sleepTime;
		long overSleepTime = 0L;
		int noDelays = 0;
		long excess = 0L;
		
		beforeTime = System.nanoTime();
		
		running = true;
		while(running){
			gameUpdate();
			gameRender();
			paintScreen();
			
			
			
			afterTime = System.nanoTime();
			timeDiff = afterTime - beforeTime;
			sleepTime = (PERIOD - timeDiff) - overSleepTime;;
			
			if(sleepTime > 0){
				try{
					Thread.sleep(sleepTime/1000000L);  // nano -> ms
				}catch(InterruptedException e){ e.printStackTrace(); }
				overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
			}else{
				excess -= sleepTime;
				overSleepTime = 0L;
				
				if(++noDelays >= NO_DELAYS_PER_YIELD){
					Thread.yield();  // give another thread a chance to run
					noDelays = 0;
				}
			}
			beforeTime = System.nanoTime();
			
			/* If frame animation is taking too long, update the game state
	           without rendering it. */
			int skips = 0;
			while((excess > PERIOD) && (skips < MAX_FRAME_SKIPS)){
				excess -= PERIOD;
				gameUpdate();   // update state but don't render
				skips++;
			}
			this.requestFocus();
		}
	}
	
	/**
	 * Update game state.
	 */
	private void gameUpdate(){
		if(!isPaused && !gameOver){
			tank.move();
		}
	}
	
	/**
	 * Draw the current frame to an image buffer.
	 */
	private void gameRender(){
		if(dbImage == null){
			dbImage = createImage(PWIDTH, PHEIGHT);
			dbg = dbImage.getGraphics();
		}
		
		dbg.setColor(Color.white);
		dbg.fillRect(0, 0, PWIDTH, PHEIGHT);
		
		tank.draw(dbg);
	}
	
	/**
	 * Actively render the buffer image to the screen.
	 */
	private void paintScreen(){
		Graphics g = this.getGraphics();
		if(g != null && dbImage != null){
			g.drawImage(dbImage, 0, 0, null);
			Toolkit.getDefaultToolkit().sync(); // sync the display on some systems
		}
		g.dispose();
	}
}
